﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

using INSTA.Library;


namespace INSTA.BankApplication.DAL
{
    /// <summary>
    /// Abstract base class for all types that are 
    /// mapped to the database.
    /// </summary>
    public abstract class DataMapper
    {

        public const int UNDEFINED_ID = -1;

        private int _id;
        private Dictionary<string, MappedProperty> _mappedProperties;

        private AbstractDbAccessor _dbAccessor;



        /// <summary>
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
        /// </summary>
        protected DataMapper() : this(UNDEFINED_ID)
        {}

        /// <summary>
        /// Initializes a new instance of the <see cref="DataMapper"/> class.
        /// </summary>
        /// <param name="id">The identifier.</param>
        protected DataMapper(int id)
        {
            Id = id;

            // Fills the dictionary of MappedProperty..
            RefreshMappedProperties();

            // instanciate the Database Accessor via our factory..
            // Creates the correct type of DbAccessor (SqlServer / MySql)..
            _dbAccessor = DbAccessorFactory.Create(this);

            // If the user provided a valid Id...
            if (Id != UNDEFINED_ID)
            {
                // Load the record for that Id.
                Read();
            }


        }


        /**********************************************************************
         * 
         * Methods
         * 
         **********************************************************************/



        /// <summary>
        /// Fills the list of MappedProperty objects.
        /// </summary>
        protected void RefreshMappedProperties()
        {
            // Let's instanciate the dictionary of mapped properties..
            _mappedProperties = new Dictionary<string, MappedProperty>();

            // let's loop on all properties
            foreach (var prop in this.GetType().GetProperties())
            {
                // if the current property is decorated with a FieldAttribute..
                if (prop.IsDecoratedWith<FieldAttribute>())
                {
                    // instanciate a new MappedProperty..
                    var mappedProperty = new MappedProperty(prop, this);

                    // And store it in the dictionary with the property Name for the key,
                    // and the MappedProperty for the value.
                    _mappedProperties.Add(prop.Name, mappedProperty);
                }
            }
        }


        /// <summary>
        /// Deletes this instance from the database.
        /// </summary>
        /// <returns>true on success.</returns>
        public virtual bool Delete()
        {
            bool result = false;

            if (_id == UNDEFINED_ID)
            {
                string message = string.Format("The Key {0} was not found in the table {1}",
                                                Id,
                                                DbAccessor.SqlGenerator.GetTableName());
                throw new KeyNotFoundException(message);
            }

            result = DbAccessor.Delete();

            // Reset the instance's Id property
            // back to default value, to indicate
            // that this instance is not contained
            // in the database anymore.
            if (result)
            {
                var primaryKeyProperty = GetPrimaryKeyProperty();

                // Set back the ID to -1 to indicate that this instance
                // does not correspond to a database record.
                primaryKeyProperty.PropertyValue = UNDEFINED_ID;
            }

            return result;
        }


        /// <summary>
        /// Saves this instance in the database.
        /// </summary>
        /// <returns>true on success.</returns>
        public virtual bool Save()
        {
            bool result = false;

            // Gets the MappedProperty that 
            // represents the primary key..
            var primaryKeyProperty = GetPrimaryKeyProperty();

            // If the current instance does not 
            // comes from the database, let's INSERT !
            if ((int)primaryKeyProperty.PropertyValue == UNDEFINED_ID)
            {
                int lastGeneratedId = UNDEFINED_ID;

                // performs the INSERT and get back
                // the last generated Id value..
                lastGeneratedId = DbAccessor.Insert();

                result = (lastGeneratedId > 0);

                // On operation success, set the primary
                // key value to the last Id generated 
                // by the INSERT..
                if (result)
                {
                    primaryKeyProperty.PropertyValue = lastGeneratedId;
                }
            }
            // If the current instance 
            // comes from the database, let's UPDATE ! 
            else
            {
                result = DbAccessor.Update();
            }

            return result;
        }


        /// <summary>
        /// Reads this instance from the database
        /// and the record values to the current
        /// instance's mapped properties..
        /// </summary>
        /// <returns>true on success</returns>
        protected void Read()
        {
            // Let's get the DataTable containing
            // the record for this instance..
            DataTable table = DbAccessor.Select();

            // Check the DataTable for consistency..
            int rowCount = table.Rows.Count;

            // CASE 1: NO RECORD FOUND
            // on record not found for the given Id..
            if (rowCount == 0)
            {
                string message = string.Format("The Key {0} was not found in the table {1}",
                                                Id,
                                                DbAccessor.SqlGenerator.GetTableName());
                throw new KeyNotFoundException(message);
            }

            // CASE 2: MULTIPLE RECORDS FOUND
            // on several records found for 
            // the given Id (malformed database)..
            if (rowCount > 1)
            {
                string message = string.Format("The Key {0} is not unique in the database",
                                               _id.ToString());
                throw new DuplicateKeyFoundException(message);
            }

            // CASE 3: ONE RECORD FOUND --> OK
            // Map DataTable's Row values to 
            // current instance's property values.
            foreach (DataColumn col in table.Columns)
            {
                foreach (MappedProperty mappedProperty in _mappedProperties.Values)
                {
                    if (mappedProperty.FieldName == col.Caption &&
                        !mappedProperty.IsPrimaryKey)
                    {
                        mappedProperty.PropertyValue = table.Rows[0][col.Caption];
                    }
                }
            }
        }









        /// <summary>
        /// Gets the primary key property.
        /// </summary>
        /// <returns>MappedProperty.</returns>
        private MappedProperty GetPrimaryKeyProperty()
        {
            var primaryKeyProperty = (from prop in MappedProperties.Values
                                      where prop.IsPrimaryKey
                                      select prop).Single();

            return primaryKeyProperty;
        }


        
        
        /**********************************************************************
         * 
         * Properties
         * 
         **********************************************************************/



        /// <summary>
        /// Gets or sets database Id.
        /// </summary>
        [Field("Id", IsPrimaryKey =  true)]
        public int Id
        {
            get         { return _id; }
            set         
            { 
                //TODO : check why it does not work when the setter is private.
                _id = value; 
            }
        }

        /// <summary>
        /// Gets the mapped properties.
        /// </summary>
        /// <value>The mapped properties.</value>
        public Dictionary<string, MappedProperty> MappedProperties
        {
            get { return _mappedProperties; }
            
        }

        /// <summary>
        /// Gets the database accessor.
        /// </summary>
        /// <value>The database accessor.</value>
        public AbstractDbAccessor DbAccessor
        {
            get { return _dbAccessor; }
        }
    }
}
